package scuts.core;
import scuts.Assert;
import scuts.core.types.ProgressiveFuture;

class CollectArrayProgressiveFuture
{
  public static inline function collect < S,T > (a:Array<ProgressiveFuture<S>>, f:S->Array<ProgressiveFuture<T>>):Array<ProgressiveFuture<Array<T>>>
  {
    return CollectIterableProgressiveFuture.collectFromArrayToArray(a, f);
  }
}

class CollectIterableProgressiveFuture
{
  public static function collectFromArrayToArray < S,T > (a:Iterable<ProgressiveFuture<S>>, f:S->Array<ProgressiveFuture<T>>):Array<ProgressiveFuture<Array<T>>>
  {
    var res = [];
    
    for (e in a) {
      var fut = CollectProgressiveFuture.collectFromArrayToArray(e, f);
      res.push(fut);
    }
    return res;
  }
}

class CollectProgressiveFuture
{
  public static function onCompleteHandler <S,T>(fut:ProgressiveFuture<Array<T>>, num:Int, all:Iterable<ProgressiveFuture<T>>, firstFutureRatio:Float) {
    if (num == 0) {
        // this sets progress to 1.0 automatically
        fut.deliver([]);
      } else {
        var allRatio = 1.0-firstFutureRatio;
        var cur = 0;
        var d = [];
        var percentages = [];
        
        function updateProgress () {
          var p = 0.0;
          for (i in percentages) p += i;
          fut.setProgress(firstFutureRatio + (p/num) * allRatio);
        }
        var z = 0;
        for (i in all) {
          
          i.deliverTo(function (r1) {
            d.push(r1);
            if (++cur == num) fut.deliver(d);
          });
          i.ifCanceled(function () {
            fut.cancel();
          });
          
          var index = z;
          percentages[index] = 0.0;
          i.onProgress(function (p) {
            percentages[index] = p;
            updateProgress();
          });
          z++;
        }
      }
  }
  
  public static function collectFromArrayToArray < S,T > (e:ProgressiveFuture<S>, f:S->Array<ProgressiveFuture<T>>, ?firstFutureRatio:Float = 0.5):ProgressiveFuture<Array<T>>
  {
    Assert.assertFloatInRange(firstFutureRatio, 0.0, 1.0);
    var fut = new ProgressiveFuture();
    
    e.deliverTo(function (r) {
      var all = f(r);
      var num = all.length;
      onCompleteHandler(fut, num, all, firstFutureRatio);
    });
    
    // we don't know how many futures are returned by f, so we cannot
    // calculate the percentage correctly. Thus the percentage for the first 
    // future can be set through firstFutureRatio
    e.onProgress(function (p) {
      fut.setProgress(p*firstFutureRatio);
    });
    
    e.ifCanceled(function () {
      fut.cancel();
    });
    return fut;
  }
  
  public static function collectFromIterableToArray < S,T > (e:ProgressiveFuture<S>, f:S->Iterable<ProgressiveFuture<T>>, ?firstFutureRatio:Float = 0.5):ProgressiveFuture<Array<T>>
  {
    Assert.assertFloatInRange(firstFutureRatio, 0.0, 1.0);
    var fut = new ProgressiveFuture();
    
    e.deliverTo(function (r) {
      var all = f(r);
      var num = { var i = 0; for (_ in all) i++; i;};
      onCompleteHandler(fut, num, all, firstFutureRatio);
    });
    // we don't know how many futures are returned by f, so we cannot
    // calculate the percentage correctly. Thus the percentage for the first 
    // future can be set through firstFutureRatio
    e.onProgress(function (p) {
      fut.setProgress(p*firstFutureRatio);
    });
    e.ifCanceled(function () {
      fut.cancel();
    });
    return fut;
  }
}